#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include <unistd.h>
#include <fcntl.h>

#include "debug.h"
#define MAXBUF 1024
/*
gcc -Wall ssl_client.c -lssl -o client
gcc -Wall ssl_server.c -lssl -o server
run command:
./server 7838 1 cacert.pem privkey.pem
./client 127.0.0.1 7838

run below command genernate cacert.pem and privkey.pem
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

*/

void ShowCerts(SSL * ssl)
{
    X509 *cert;
    char *line;

    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) {
        printf("Certification information:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("֤cert: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("issue: %s\n", line);
        free(line);
        X509_free(cert);
    } else
        printf("no certification information\n");
}
/********************************************************
*filename: ssl-client.c
*purpose: use OpenSSL libarary base on IP layer SSL enc communication
*wrote by: zhoulifa(zhoulifa@163.com) http://zhoulifa.bokee.com)
*date time:2007-02-02 20:10
*Note: 
* base on GPL
*Thanks to:Google
*Hope:
*********************************************************************/
#define TEST1 1

int crSocketRecv(int socketfd, SSL *ssl, char *recvBuf, int recvLen,
        int *recvCount, int recvTimewait) {
    int ret = 0, count = 0, totalCount = 0, fcntlFlag = 0;
    fd_set rfds, wfds;
    struct timeval timeout;
    
    fcntlFlag = fcntl(socketfd, F_GETFL);
    fcntl(socketfd, F_SETFL, fcntlFlag | O_NONBLOCK);
    fcntlFlag = fcntlFlag | O_NONBLOCK;
    
    bzero(recvBuf, recvLen);
    
    timeout.tv_sec = recvTimewait;
    timeout.tv_usec = 0;
    for (; totalCount < recvLen; count = 0) {
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_SET(socketfd, &rfds);
        //START
#if TEST1       
        if (ssl) {
            FD_SET(socketfd, &wfds);
        }
#endif
        if ((ret = select((socketfd + 1), &rfds, &wfds, NULL, &timeout)) <= 0){
            if (ret == 0) {
                //DEBUGRED( "Connection Timeout! errno:%d\n", errno);
                errno = ETIMEDOUT;
                fcntl(socketfd, F_SETFL, fcntlFlag);
                return CR_RetCode_SOCKET_RECV_TIMEOUT;
            } else {
                if (errno == EINTR) {
                    continue;
                } else {
                   // DEBUGRED( "Select return -1! errno:%d\n", errno);
                    fcntl(socketfd, F_SETFL, fcntlFlag);
                    return CR_RetCode_SOCKET_RECV_FAILED;
                }
            }
        }
#if TEST1
        if (FD_ISSET(socketfd, &rfds) || FD_ISSET(socketfd, &wfds)) {
#else
        if (FD_ISSET(socketfd, &rfds)) {
#endif
            for (; totalCount < recvLen;) {
                if (ssl) {
                    count = SSL_read(ssl, recvBuf + totalCount, recvLen
                            - totalCount);
                   // DEBUGRED( "SSL read\n");
                } else {
                    count = recv(socketfd, recvBuf + totalCount, recvLen
                            - totalCount, MSG_NOSIGNAL);
                }
                if (count == -1) {
                    if (errno != EWOULDBLOCK) {
                        //DEBUGRED( "errno:%d\n", errno);
                        fcntl(socketfd, F_SETFL, fcntlFlag);
                        return CR_RetCode_SOCKET_RECV_FAILED;
                    } else {
                        if (recvCount) {
                            *recvCount = totalCount;
                        }
                        fcntl(socketfd, F_SETFL, fcntlFlag);
                        return CR_RetCode_OK;
                    }
                } else if (count == 0) {
                    //DEBUGRED("%s\n", strerror(errno));
                    fcntl(socketfd, F_SETFL, fcntlFlag);
                    return CR_RetCode_SOCKET_PEER_CLOSE;
                } else {
                    if (count > 0) {
                        totalCount += count;
                        timeout.tv_sec = recvTimewait;
                        timeout.tv_usec = 0;
                    }
                }
            }
        }
        END
    }
    if (recvCount) {
        *recvCount = totalCount;
    }
    fcntl(socketfd, F_SETFL, fcntlFlag);

    return CR_RetCode_OK;
}

int main(int argc, char **argv)
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    SSL_CTX *ctx;
    SSL *ssl;
    int  fcntlFlag = 0;
    fd_set rfds, wfds;
    struct timeval timeout;
    int recvTimewait = 10;
    int count = 0;
    int recvCount = 0;
    int recvTotal = 0;
   
    int port = SSL_PORT;
    char server[20] = "127.0.0.1";
    int ret =0;

    PRINTCLEAN();

#define USAGE  "Please follow the below format:\n"\
                "\t%s IP port\n\texample: %s 127.0.0.1 %d\n"\
                "The maxbuf is %d\n"

    if (argc != 3) {
        printf(USAGE, argv[0], argv[0], SSL_PORT, MAXBUF);
        printf("using default value\n");
    } else {
        strncpy(server,argv[1],sizeof(server));
        port = atoi(argv[2]);  
    }

    /* SSL library init, refer to ssl-server.c */
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* create one socket for tcp  */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }
    printf("socket created\n");

    /* init server ipaddres and port */
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    if (inet_aton(server, (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(server);
        exit(errno);
    }
    printf("address created\n");

    /* connect to server */
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }
    printf("server connected\n");

    /* base on ctx create new SSL */
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, sockfd);
    /* build SSL connection */
    if (SSL_connect(ssl) == -1)
        ERR_print_errors_fp(stderr);
    else {
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        ShowCerts(ssl);
    }
    

    /* accept message the max is MAXBUF */
    bzero(buffer, MAXBUF + 1);
    /* accept server massge */
    //DEBUGRED("before read\n");
    //len = SSL_read(ssl, buffer, MAXBUF);
    DEBUGGREEN("begin read\n");
    recvTimewait = 30;
    recvCount = 13;
    recvTotal = 0;

    bzero(buffer, MAXBUF + 1);
    snprintf(buffer, sizeof(buffer), "GET https://%s:443/state/%s", server,
        "00:13:92:20:C0:40");
    /*send message to server */
    len = SSL_write(ssl, buffer, strlen(buffer));
    if (len < 0)
        printf
        ("send '%s'failed errno:%d, errstr:'%s'\n",
        buffer, errno, strerror(errno));
    else
        printf("send '%s'success, send %d bytes\n",
        buffer, len);
    
    time_t runningTime = time(NULL);
#if 1
    while (recvTotal < recvCount) {
        if ((time(NULL) - runningTime) > recvTimewait) {
                DEBUGRED( "socketRecv Timeout!\n");
                break;
                //return CR_RetCode_HTTP_RECV_TIMEOUT;
         }
        if ((ret = crSocketRecv (sockfd, ssl, buffer + recvTotal,
                recvCount - recvTotal, &count, recvTimewait)) != CR_RetCode_OK) {
            DEBUGRED( "ret:%d\n", ret);
            break;
            //return ret;
        } else {
            if (count > 0) {
                recvTotal += count;
            }
           
        }
    }
    DEBUGRED(  "TotalCount %d\n", recvTotal);
#else    
    while ((ret = crSocketRecv(sockfd, ssl, buffer+recvTotal, recvCount, &count,
            recvTimewait)) == CR_RetCode_OK){
        DEBUGGREEN("read count:%d\n",count);
        len = count;
        recvTotal += count;
        recvCount -= count;
        

    }
#endif

    DEBUGGREEN("read ret:%d\n",ret);
    len = recvTotal;
    if (recvTotal < MAXBUF -1) {
        buffer[recvTotal-1] = '\n';
    } else {
        strcpy(buffer,"failed\n");
    }
        
    if (len > 0)
        printf("recv success:'%s', length is %d\n",
        buffer, len);
    else {
        printf
        ("recv failed error code:%d error message: '%s'\n",
        errno, strerror(errno));
        goto finish;
    }        
    DEBUGGREEN("after read\n");
    

    finish:
    /* close connection */
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);
    SSL_CTX_free(ctx);
    return 0;
}

